package bancosys.tec.datadigester.input;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Resetable FileInputStream, used by DataDigesterController. Allows reading a input stream more than one time (needed for parsing and
 * logging), and protects the hidden InputStream from closing.
 * 
 * @author lundberg
 */
public class ResetableFileInputStream extends InputStream {

    private final File file;

    private InputStream delegate;

    private long marked;

    private long read;

    /**
     * Usado para compartilhar o campo {@link #shareCount} entre várias instâncias.
     */
    private final ResetableFileInputStream original;

    /**
     * Determina quando ninguem mais está usando o arquivo (quando valor for zero poderá ser excluído).
     */
    private final AtomicInteger shareCount = new AtomicInteger(1);

    /**
     * True se este stream liberou o arquivo para poder ser excluído.
     */
    private boolean erased;

    /**
     * Construtor
     * 
     * @param in arquivo de origem
     */
    public ResetableFileInputStream(File in) {
        this.file = in;
        this.original = this;
    }

    /**
     * Construtor.
     * 
     * @param in stream a ser usado como arquivo de origem
     */
    public ResetableFileInputStream(ResetableFileInputStream in) {
        this.file = in.file;
        this.read = in.read;
        this.marked = in.marked;
        this.original = in.original;
        this.original.shareCount.incrementAndGet();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void mark(int readlimit) {
        this.marked = this.read;
    }

    /**
     * Resets the inputStream to the previously marked position.
     * 
     * @throws IOException IOException
     */
    @Override
    public synchronized void reset() throws IOException {
        this.close();
        if (this.marked > 0) {
            this.skip(this.marked);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long skip(long n) throws IOException {
        final long skip = this.getOrOpenDelegate().skip(n);
        this.read += skip;
        return skip;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized int available() throws IOException {
        if (this.delegate == null) {
            return (int) this.file.length();
        }
        return this.delegate.available();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void close() throws IOException {
        try {
            if (this.delegate != null) {
                this.delegate.close();
            }
        } finally {
            this.read = 0;
            this.delegate = null;
        }
    }

    /**
     * @return InputStream
     * @throws IOException e
     */
    private synchronized InputStream getOrOpenDelegate() throws IOException {
        if (this.delegate == null) {
            this.delegate = new FileInputStream(this.file);
        }
        return this.delegate;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized int read() throws IOException {
        final int b = this.getOrOpenDelegate().read();
        if (b >= 0) {
            this.read++;
        }
        return b;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized int read(byte[] b, int off, int len) throws IOException {
        final int total = this.getOrOpenDelegate().read(b, off, len);
        this.read += total;
        return total;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean markSupported() {
        return true;
    }

    /**
     * Returs the file
     * 
     * @return {@link File}
     */
    public File getFile() {
        return this.file;
    }

    /**
     * Fecha o stream e excluí o arquivo usado como origem. <br>
     * Erros durante o fechamento do arquivo serão ignorados.
     * 
     * @return True se o arquivo foi excluído
     */
    public synchronized boolean delete() {
        try {
            if (this.delegate != null) {
                this.delegate.close();
            }
        } catch (IOException e) {
            // ignora
        } finally {
            this.read = 0;
            this.delegate = null;
        }
        if (!this.erased) {
            this.erased = true;
            if (this.original.shareCount.decrementAndGet() == 0) {
                return this.getFile().delete();
            }
        }
        return false;
    }

}
